//
//  APIManager.swift
//  
//
//  Created by bo wang on 2020/11/18.
//  Copyright © 2020 wangbo. All rights reserved.
//

import UIKit
import Alamofire

/// 上传文件时的key
enum APIFileInfoKey: String {
    /// 上传文件时数据body对应的key，不能为nil。
    case name
    /// 上传文件名。可以为nil，如果ext不为nil，则将ext添加到 name 的后面。否则使用 name。
    case filename
    /// 上传的文件扩展名。默认为nil
    case ext
    /// 上传的文件类型。默认为 text/plain
    case mimetype
    /// 上传的文件数据。有 filePath 时忽略此值
    case binary = "data"
    /// 上传的文件路径。
    case filePath
}

typealias APITASK = DataRequest
typealias APICompletion<T: NSObject> = (_ result: APIResult<T>) -> Void
typealias APINormalCompletion = APICompletion<UndefinedModel>

extension LogTag {
    static let api = LogTag("API")
}

let API = APIManager.shared

class APIManager: NSObject {
    
    @objc static let shared: APIManager = APIManager()
    
    /// Default items count per page
    static let pageCount = 10
    private var apiUrl: String { "https://app-pro.sgztech.net/mobile-app-api" }
    private var network: NetworkReachabilityManager?
    
    private override init() {
        self.network = NetworkReachabilityManager()
        self.network?.startListening(onUpdatePerforming: { _ in })
    }
    
    var enableLog = true
    var validStatusCodes = 200..<404
    
    var networkStatus: String {
        switch network?.status {
        case .none, .some(.unknown): return "unknown"
        case .some(.notReachable): return "noNetwork"
        case .some(.reachable(.cellular)): return "cellular"
        case .some(.reachable(.ethernetOrWiFi)): return "wifi"
        }
    }
    
    var presetParams: [String: Any] {
        var params: [String: Any] = [:]
        params["token"] = AccountManager.shareAccount().token()
//        params["platform"] = UIDevice.current.systemName
//        params["systemVersion"] = UIDevice.current.systemVersion
//        params["channel"] = CHANNEL
//        params["network"] = networkStatus
        return params
    }
    
    var presetHeaders: [String: String] {
        var headers: [String: String] = [:]
        
        if let token = AccountManager.shareAccount().token() {
            headers["Authorization"] = "Bearer \(token)"
        }
//        headers["app_os"] = "iOS"
//        headers["app_version"] = APP_VERSION
        return headers
    }
    
    private static func logoutIfTokenExpired<T>(_ result: APIResult<T>) {
        if result.statusCode == 401 || result.resultCode == -4 {
            AppDelegate.shared.onLoginInvalid()
        }
    }
    
    private static func urlDesc(_ url: URL?) -> String {
        guard let str = url?.absoluteString else { return "" }
        return str.components(separatedBy: "?").first?.components(separatedBy: "://").last ?? ""
    }
    
    private func logResponse<T>(_ resp: AFDataResponse<Data>, result: APIResult<T>) {
        guard enableLog else { return }
        let api = Self.urlDesc(resp.response?.url ?? resp.request?.url)
        if !result.suc {
            let desc = resp.debugDescription
            Log.error(tag: .api, api + " Failed! Info:\n" + desc + "\n")
        } else {
            let respTime = resp.metrics?.taskInterval.duration ?? 0
            let time = respTime + resp.serializationDuration
            let timeDesc = time >= 1 ? String(format: "%.3fs", time) : "\(Int(time * 1000))ms"
            Log.info(tag: .api, api + " Completed. Duration: " + timeDesc)
        }
    }
    
    private func respHandler<T>(_ completion: APICompletion<T>?) -> (AFDataResponse<Data>) -> Void {{ (resp) in
        if case .failure(let error) = resp.result,
           case .explicitlyCancelled = error {
            // 这是被标记为取消的请求，不再处理
            return
        }
        let result = APIResult<T>(resp: resp)
        self.logResponse(resp, result: result)
        completion?(result)
        Self.logoutIfTokenExpired(result)
    }}
    
    private func apiurl(_ api: String) -> URL? {
        guard var base = URL(string: self.apiUrl) else { return nil }
        if !base.path.isEmpty, !base.absoluteString.hasSuffix("/") {
            base = base.appendingPathComponent("")
        }
        return URL(string: api, relativeTo: base)
    }
    
    private static func dataValue(of any: Any) -> Data {
        if let data = any as? Data {
            return data
        }
        if let str = safe_string_or_nil(any),
           let data = str.data(using: .utf8) {
            return data
        }
        if JSONSerialization.isValidJSONObject(any),
           let data = try? JSONSerialization.data(withJSONObject: any, options: .fragmentsAllowed) {
            return data
        }
        return Data()
    }
    
    /// 向服务端发起普通GET请求，URLEncoding方式。超时时间30秒。
    /// - Parameters:
    ///   - api: API名
    ///   - params: 自定义请求参数
    ///   - enablePresetParams: 是否使用预设参数，默认为true
    ///   - headers: 自定义请求头
    ///   - enablePresetHeaders: 是否使用预设请求头，默认为true
    ///   - completion: 请求响应回调
    func GET<T>(_ api: String,
                _ params: [String: Any],
                enablePresetParams: Bool = true,
                headers: [String: String] = [:],
                enablePresetHeaders: Bool = true,
                _ completion: APICompletion<T>?)
    -> APITASK? {
        guard let url = apiurl(api) else { return nil }
        let p = enablePresetParams ? params.merging(presetParams) { old, _ in old } : params
        let h = enablePresetHeaders ? headers.merging(presetHeaders) { (old, _) in old } : headers
        let request = AF
            .request(url,
                     parameters: p,
                     headers: HTTPHeaders(h),
                     requestModifier: { $0.timeoutInterval = 30 })
            .validate(statusCode: validStatusCodes)
        Log.debug(tag: .api, "GET: \(api)")
        request.responseData(completionHandler: respHandler(completion))
        return request
    }
    
    func DELETE<T>(_ api: String,
                   _ params: [String: Any],
                   enablePresetParams: Bool = true,
                   headers: [String: String] = [:],
                   enablePresetHeaders: Bool = true,
                   _ completion: APICompletion<T>?)
    -> APITASK? {
        guard let url = apiurl(api) else { return nil }
        let p = enablePresetParams ? params.merging(presetParams) { old, _ in old } : params
        let h = enablePresetHeaders ? headers.merging(presetHeaders) { (old, _) in old } : headers
        let request = AF
            .request(url,
                     method: .delete,
                     parameters: p,
                     encoding: JSONEncoding.default,
                     headers: HTTPHeaders(h),
                     requestModifier: { $0.timeoutInterval = 30 })
            .validate(statusCode: validStatusCodes)
        Log.debug(tag: .api, "DELETE: \(api)  Params:\(p.description)")
        request.responseData(completionHandler: respHandler(completion))
        return request
    }
    
    func POST<T>(_ api: String,
                 _ params: [String: Any],
                 enablePresetParams: Bool = true,
                 headers: [String: String] = [:],
                 enablePresetHeaders: Bool = true,
                 _ completion: APICompletion<T>?)
    -> APITASK? {
        guard let url = apiurl(api) else { return nil }
        let p = enablePresetParams ? params.merging(presetParams) { old, _ in old } : params
        let h = enablePresetHeaders ? headers.merging(presetHeaders) { (old, _) in old } : headers
        let request = AF
            .request(url,
                     method: .post,
                     parameters: p,
                     encoding: JSONEncoding.default,
                     headers: HTTPHeaders(h),
                     requestModifier: { $0.timeoutInterval = 30 })
            .validate(statusCode: validStatusCodes)
        Log.debug(tag: .api, "POST: \(api)  params:\(p.description)")
        request.responseData(completionHandler: respHandler(completion))
        return request
    }
    
    func PUT<T>(_ api: String,
                _ params: [String: Any],
                enablePresetParams: Bool = true,
                headers: [String: String] = [:],
                enablePresetHeaders: Bool = true,
                _ completion: APICompletion<T>?)
    -> APITASK? {
        guard let url = apiurl(api) else { return nil }
        let p = enablePresetParams ? params.merging(presetParams) { old, _ in old } : params
        let h = enablePresetHeaders ? headers.merging(presetHeaders) { (old, _) in old } : headers
        let request = AF
            .request(url,
                     method: .put,
                     parameters: p,
                     encoding: JSONEncoding.default,
                     headers: HTTPHeaders(h),
                     requestModifier: { $0.timeoutInterval = 30 })
            .validate(statusCode: validStatusCodes)
        Log.debug(tag: .api, "PUT: \(api)  params:\(p.description)")
        request.responseData(completionHandler: respHandler(completion))
        return request
    }
    
    func POSTDATA<T>(_ api: String,
                     _ params: [String: Any],
                     enablePresetParams: Bool = true,
                     data: [String: Any],
                     headers: [String: String] = [:],
                     enablePresetHeaders: Bool = true,
                     _ completion: APICompletion<T>?)
    -> APITASK? {
        guard !data.isEmpty else {
            return POST(api,
                        params,
                        enablePresetParams: enablePresetParams,
                        headers: headers,
                        enablePresetHeaders: enablePresetHeaders,
                        completion)
        }
        guard let url = apiurl(api) else { return nil }
        let p = enablePresetParams ? params.merging(presetParams) { old, _ in old } : params
        let h = enablePresetHeaders ? headers.merging(presetHeaders) { (old, _) in old } : headers
        let form: (MultipartFormData) -> Void = { formData in
            p.forEach({ formData.append(Self.dataValue(of: $1), withName: $0) })
            data.forEach({ formData.append(Self.dataValue(of: $1), withName: $0) })
        }
        let request = AF
            .upload(multipartFormData: form,
                    to: url,
                    headers: HTTPHeaders(h),
                    requestModifier: { $0.timeoutInterval = 120 })
            .validate(statusCode: validStatusCodes)
        Log.debug(tag: .api, "POSTDATA: \(api)")
        request.responseData(completionHandler: respHandler(completion))
        return request
    }
    
    /// 向服务端发起POST请求。二进制数据可以从文件上传。超时时间5分钟。
    /// - Parameters:
    ///   - api: API名称
    ///   - params: 请求参数。value必须是可以转为Data的内容。不能转的将被忽略。
    ///   - files: 请求中额外携带的文件数据。数据格式必须满足 APIFileInfoKey 中定义的要求。
    ///   - completion: 请求回调
    /// - Returns: 请求体
    func UPLOAD<T>(_ api: String,
                   _ params: [String: Any],
                   enablePresetParams: Bool = true,
                   files: [[APIFileInfoKey: Any]],
                   headers: [String: String] = [:],
                   enablePresetHeaders: Bool = true,
                   _ completion: APICompletion<T>?)
    -> APITASK? {
        guard !files.isEmpty else {
            return POST(api,
                        params,
                        enablePresetParams: enablePresetParams,
                        headers: headers,
                        enablePresetHeaders: enablePresetHeaders,
                        completion)
        }
        guard let url = apiurl(api) else { return nil }
        let p = enablePresetParams ? params.merging(presetParams) { old, _ in old } : params
        let h = enablePresetHeaders ? headers.merging(presetHeaders) { (old, _) in old } : headers
        
        let form: (MultipartFormData) -> Void = { formData in
            p.forEach({ formData.append(Self.dataValue(of: $1), withName: $0) })
            files.forEach { (info) in
                let name = safe_string(info[.name], default: "file")
                let _filename = safe_string_or_nil(info[.filename])
                let ext = safe_string_or_nil(info[.ext])
                let type = safe_string(info[.mimetype], default: "text/plain")
                let data = info[.binary] as? Data
                let path = safe_string_or_nil(info[.filePath])
                
                var filename: String!
                if let f = _filename, !f.isEmpty {
                    filename = f
                } else if let e = ext, !e.isEmpty {
                    filename = (name as NSString).appendingPathExtension(e)
                } else {
                    filename = name
                }
                
                if let p = path, FileManager.default.fileExists(atPath: p) {
                    let url = URL(fileURLWithPath: p)
                    formData.append(url, withName: name, fileName: filename, mimeType: type)
                } else if let d = data {
                    formData.append(d, withName: name, fileName: filename, mimeType: type)
                }
            }
        }
        let request = AF
            .upload(multipartFormData: form,
                    to: url,
                    headers: HTTPHeaders(h),
                    requestModifier: { $0.timeoutInterval = 300 })
            .validate(statusCode: validStatusCodes)
        Log.debug(tag: .api, "UPLOAD: \(api)")
        request.responseData(completionHandler: respHandler(completion))
        return request
    }
}
